import {
    PublicKey,
    sendAndConfirmTransaction,
    Transaction,
    TransactionInstruction,
    VersionedTransaction
} from '@solana/web3.js';
import {NATIVE_MINT, TOKEN_PROGRAM_ID} from "@solana/spl-token";
import * as path from 'path';
import * as fs from 'fs';
import {connection, owner, programId, mintBase, grpcUrl,amount,initSdk, slippage, txVersion,fetchTokenAccountData} from "../config2";
import WebSocket from 'ws';
import {
    AmmRpcData,
    AmmV4Keys,
    API_URLS,
    ApiV3PoolInfoConcentratedItem, ApiV3PoolInfoStandardItem, ApiV3PoolInfoStandardItemCpmm,
    ClmmKeys,
    ComputeClmmPoolInfo, CpmmKeys, CpmmRpcData, CurveCalculator, PoolUtils,
    Raydium,
    RAYMint, ReturnTypeFetchMultiplePoolTickArrays
} from "@raydium-io/raydium-sdk-v2";
import axios from "axios";
import {isValidAmm, isValidClmm, isValidCpmm} from "../amm/utils";
import BN from "bn.js";
import {printSimulateInfo} from "../util";
import Decimal from "decimal.js";


// 代币的 Mint 地址（以 USDC 为例）
// const tokenMintAddress = new PublicKey('EPjFWdd5Au6j7iHk5PRfnGzWi79GmY1U4seRER16X8VZ');
// const tokenMintAddress = new PublicKey('7atgF8KQo4wJrD5ATGX7t1V2zVvykPJbFfNeVf1icFv1');
// const tokenMintAddress = new PublicKey('BqmnTUm88ohqSsEfLMjLAMcvrRzq1JwJmHLBTrBvmeme');
const tokenMintAddress = new PublicKey('HRw8mqK8N3ASKFKJGMJpy4FodwR3GKvCFKPDQNqUNuEP'); //46144
let raydium: Raydium = {} as Raydium
let maxBuy:number = 1
let curBuy:number = 0


interface SwapCompute {
    id: string
    success: true
    version: 'V0' | 'V1'
    openTime?: undefined
    msg: undefined
    data: {
        swapType: 'BaseIn' | 'BaseOut'
        inputMint: string
        inputAmount: string
        outputMint: string
        outputAmount: string
        otherAmountThreshold: string
        slippageBps: number
        priceImpactPct: number
        routePlan: {
            poolId: string
            inputMint: string
            outputMint: string
            feeMint: string
            feeRate: number
            feeAmount: string
        }[]
    }
}


export async function apiSwap(mint:string)  {
    const inputMint = NATIVE_MINT.toBase58()
    const outputMint = mint // RAY
    const amountIn = amount
    const slippageIn = slippage // in percent, for this example, 0.5 means 0.5%
    const txVersion: string = 'V0' // or LEGACY
    const isV0Tx = txVersion === 'V0'

    const [isInputSol, isOutputSol] = [inputMint === NATIVE_MINT.toBase58(), outputMint === NATIVE_MINT.toBase58()]

    const { tokenAccounts } = await fetchTokenAccountData()
    const inputTokenAcc = tokenAccounts.find((a) => a.mint.toBase58() === inputMint)?.publicKey
    const outputTokenAcc = tokenAccounts.find((a) => a.mint.toBase58() === outputMint)?.publicKey

    if (!inputTokenAcc && !isInputSol) {
        console.error('do not have input token account')
        return
    }

    // get statistical transaction fee from api
    /**
     * vh: very high
     * h: high
     * m: medium
     */
    const { data } = await axios.get<{
        id: string
        success: boolean
        data: { default: { vh: number; h: number; m: number } }
    }>(`${API_URLS.BASE_HOST}${API_URLS.PRIORITY_FEE}`)

    const { data: swapResponse } = await axios.get<SwapCompute>(
        `${
            API_URLS.SWAP_HOST
        }/compute/swap-base-in?inputMint=${inputMint}&outputMint=${outputMint}&amount=${amountIn}&slippageBps=${
            slippageIn * 100
        }&txVersion=${txVersion}`
    )
    console.log("执行交易")

    const { data: swapTransactions } = await axios.post<{
        id: string
        version: string
        success: boolean
        data: { transaction: string }[]
    }>(`${API_URLS.SWAP_HOST}/transaction/swap-base-in`, {
        computeUnitPriceMicroLamports: String(data.data.default.h),
        swapResponse,
        txVersion,
        wallet: owner.publicKey.toBase58(),
        wrapSol: isInputSol,
        unwrapSol: isOutputSol, // true means output mint receive sol, false means output mint received wsol
        inputAccount: isInputSol ? undefined : inputTokenAcc?.toBase58(),
        outputAccount: isOutputSol ? undefined : outputTokenAcc?.toBase58(),
    })

    const allTxBuf = swapTransactions.data.map((tx) => Buffer.from(tx.transaction, 'base64'))
    const allTransactions = allTxBuf.map((txBuf) =>
        isV0Tx ? VersionedTransaction.deserialize(txBuf) : Transaction.from(txBuf)
    )

    console.log(`total ${allTransactions.length} transactions`, swapTransactions)

    let idx = 0
    if (!isV0Tx) {
        for (const tx of allTransactions) {
            console.log(`${++idx} transaction sending...`)
            const transaction = tx as Transaction
            transaction.sign(owner)
            const txId = await sendAndConfirmTransaction(connection, transaction, [owner], { skipPreflight: true })
            console.log(`${++idx} transaction confirmed, txId: ${txId}`)
        }
    } else {
        for (const tx of allTransactions) {
            idx++
            const transaction = tx as VersionedTransaction
            transaction.sign([owner])
            const txId = await connection.sendTransaction(tx as VersionedTransaction, { skipPreflight: true })
            const { lastValidBlockHeight, blockhash } = await connection.getLatestBlockhash({
                commitment: 'finalized',
            })
            console.log(`${idx} transaction sending..., txId: ${txId}`)
            await connection.confirmTransaction(
                {
                    blockhash,
                    lastValidBlockHeight,
                    signature: txId,
                },
                'confirmed'
            )
            console.log(`${idx} transaction confirmed`)
        }
    }
}



export async function apiSwapSell(mint:string)  {
    await sleep(1000*60*2)
    const inputMint = NATIVE_MINT.toBase58()
    const outputMint = mint // RAY

    const slippageIn = slippage // in percent, for this example, 0.5 means 0.5%
    const txVersion: string = 'V0' // or LEGACY
    const isV0Tx = txVersion === 'V0'

    const [isInputSol, isOutputSol] = [inputMint === NATIVE_MINT.toBase58(), outputMint === NATIVE_MINT.toBase58()]

    const { tokenAccounts } = await fetchTokenAccountData()
    let sellAccInfo = tokenAccounts.find((a) => a.mint.toBase58() === outputMint);
    let sellAmount = sellAccInfo?.amount.toNumber();
    if (!(sellAmount && sellAmount > 0)) {
        return
    }
    const amountIn = sellAmount

    const inputTokenAcc = tokenAccounts.find((a) => a.mint.toBase58() === inputMint)?.publicKey
    const outputTokenAcc = tokenAccounts.find((a) => a.mint.toBase58() === outputMint)?.publicKey

    if (!inputTokenAcc && !isInputSol) {
        console.error('do not have input token account')
        return
    }

    // get statistical transaction fee from api
    /**
     * vh: very high
     * h: high
     * m: medium
     */
    const { data } = await axios.get<{
        id: string
        success: boolean
        data: { default: { vh: number; h: number; m: number } }
    }>(`${API_URLS.BASE_HOST}${API_URLS.PRIORITY_FEE}`)

    const { data: swapResponse } = await axios.get<SwapCompute>(
        `${
            API_URLS.SWAP_HOST
        }/compute/swap-base-in?inputMint=${inputMint}&outputMint=${outputMint}&amount=${amountIn}&slippageBps=${
            slippageIn * 100
        }&txVersion=${txVersion}`
    )
    console.log("执行交易")

    const { data: swapTransactions } = await axios.post<{
        id: string
        version: string
        success: boolean
        data: { transaction: string }[]
    }>(`${API_URLS.SWAP_HOST}/transaction/swap-base-in`, {
        computeUnitPriceMicroLamports: String(data.data.default.h),
        swapResponse,
        txVersion,
        wallet: owner.publicKey.toBase58(),
        wrapSol: isInputSol,
        unwrapSol: isOutputSol, // true means output mint receive sol, false means output mint received wsol
        inputAccount: isInputSol ? undefined : inputTokenAcc?.toBase58(),
        outputAccount: isOutputSol ? undefined : outputTokenAcc?.toBase58(),
    })

    const allTxBuf = swapTransactions.data.map((tx) => Buffer.from(tx.transaction, 'base64'))
    const allTransactions = allTxBuf.map((txBuf) =>
        isV0Tx ? VersionedTransaction.deserialize(txBuf) : Transaction.from(txBuf)
    )

    console.log(`total ${allTransactions.length} transactions`, swapTransactions)

    let idx = 0
    if (!isV0Tx) {
        for (const tx of allTransactions) {
            console.log(`${++idx} transaction sending...`)
            const transaction = tx as Transaction
            transaction.sign(owner)
            const txId = await sendAndConfirmTransaction(connection, transaction, [owner], { skipPreflight: true })
            console.log(`${++idx} transaction confirmed, txId: ${txId}`)
        }
    } else {
        for (const tx of allTransactions) {
            idx++
            const transaction = tx as VersionedTransaction
            transaction.sign([owner])
            const txId = await connection.sendTransaction(tx as VersionedTransaction, { skipPreflight: true })
            const { lastValidBlockHeight, blockhash } = await connection.getLatestBlockhash({
                commitment: 'finalized',
            })
            console.log(`${idx} transaction sending..., txId: ${txId}`)
            await connection.confirmTransaction(
                {
                    blockhash,
                    lastValidBlockHeight,
                    signature: txId,
                },
                'confirmed'
            )
            console.log(`${idx} transaction confirmed`)
        }
    }
}



// 获取最大的持币20账户
async function getLargestTokenAccounts() {
    // let publicKey = new PublicKey("DNyd87a6uL61EpTXNS1zKwmZXxpvRvP6oYfBEaiQk3oc");
    // console.info(publicKey.toString());
    // console.info(publicKey.toBase58());
    try {
        const largestAccounts = await connection.getTokenLargestAccounts(tokenMintAddress);
        // 输出前 1000 个账户
        const accounts = largestAccounts.value.slice(0, 100);
        console.log('accounts', accounts.length);
        accounts.forEach(account => {
            // console.log(account);
            console.log(`Account Address: ${account.address.toBase58()}`);
            console.log(`Amount: ${account.amount}`);
        });
    } catch (error) {
        console.error('查询失败:', error);
    }
}


const savePath = path.join("D:\\temp", `log.txt`);



//查询指定代币持有用户，并保存到指定文件
async function getTokenAccountsByMint() {
    try {
        // 使用getParsedProgramAccounts获取所有与该代币mint相关的账户
        const tokenAccounts = await connection.getParsedProgramAccounts(
            TOKEN_PROGRAM_ID,
            {
                filters: [
                    {
                        dataSize: 165, // token账户的数据大小，165字节是标准的token账户大小
                    },
                    {
                        memcmp: {
                            offset: 0, // 数据偏移量，查找对应mint的代币
                            bytes: tokenMintAddress.toBase58(), // 过滤条件，指定mint地址
                        },
                    },
                ],
            }
        );

        console.log("tokenAccounts", tokenAccounts.length)
        // 获取前1000个账户
        // const accounts = tokenAccounts.slice(0, 10);
        const accounts = tokenAccounts.slice();
        console.log("accounts", accounts.length)
        console.log(accounts[0])
        // 输出账户地址
        accounts.forEach(account => {
            // console.log(`Account Address: ${account.pubkey.toBase58()} ===== `, (account.account.data as any).parsed.info.tokenAmount.uiAmount);
            // console.log(`Account Data:`, (account.account.data as any).parsed.info.tokenAmount);
            let num = (Math.random() * 10000000).toFixed(2)
            let str = `${account.pubkey.toBase58()},${(account.account.data as any).parsed.info.tokenAmount.uiAmountString},${num}\n`
            fs.appendFileSync(savePath, str,"utf8")
        });
    } catch (error) {
        console.error('查询失败:', error);
    }
}

export async function sayHello(): Promise<void> {
    console.log('Saying hello to');
    let programId2 = new PublicKey("H8Psa9Jc5UnrmQq2dyqZJ2qRXwYZ6CQ31v3Xse7wyh3i")
    const instruction = new TransactionInstruction({
        keys: [{pubkey: new PublicKey("6JgFpXWvSLhTKfqpystxYhGkDbmFcWfr84esxFwpS79R"), isSigner: false, isWritable: true}],
        programId: programId2,
        data: Buffer.alloc(0), // All instructions are hellos
    });
    let payer = owner
    let tx = await sendAndConfirmTransaction(
        connection,
        new Transaction().add(instruction),
        [payer],
    );
    console.log("======", tx)
}


export async function wsForCpmm(): Promise<void> {
    // Create a new WebSocket instance
    let socket:WebSocket = new WebSocket(grpcUrl);

    // Event listener for when the connection is opened
    socket.onopen = (event) => {
        console.log('WebSocket connection opened:', event);


        let msg:string = '{"jsonrpc":"2.0","id":"1","method":"blockSubscribe","params":[{"mentionsAccountOrProgram":"CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"},{"commitment":"confirmed","encoding":"jsonParsed","showRewards":true,"transactionDetails":"full"}]}'
        // Send a message to the server
        socket.send(msg);
    };

    // Event listener for receiving messages from the server
    socket.onmessage = (event) => {
        let data = JSON.parse(event.data as string);
        let err = data.params?.result.value.err
        if (!err) {
            console.log('Message from server:===',err, "===", typeof data, "===", JSON.stringify(data));
        }


        // Optionally, send an acknowledgment back to the server
        // socket.send('Received your message');
    };

    // Event listener for when an error occurs
    socket.onerror = (error) => {
        console.error('WebSocket error:', error);
    };

    // Event listener for when the connection is closed
    socket.onclose = (event) => {
        console.log('WebSocket connection closed:', event);
    };
}

export async function main():Promise<void> {
    raydium = await initSdk()
    //apiSwap("CvUo8ZZ5ZfcBNR7V7GKy2A3Sz1bSfZWrfojWxDmjiBox")
    wsForAmm()
}

export async function wsForAmm(): Promise<void> {
    // Create a new WebSocket instance
    let socket:WebSocket = new WebSocket(grpcUrl);

    // Event listener for when the connection is opened
    socket.onopen = (event) => {
        console.log('WebSocket connection opened:');


        let msg:string = '{"jsonrpc":"2.0","id":"1","method":"blockSubscribe","params":[{"mentionsAccountOrProgram":"675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"},{"commitment":"confirmed","encoding":"jsonParsed","transactionDetails":"full","maxSupportedTransactionVersion":0}]}'
        // Send a message to the server
        socket.send(msg);
        // setInterval(() => {
        //     if (socket && socket.readyState === WebSocket.OPEN) {
        //         socket.send('ping'); // 发送心跳包
        //     }
        // }, 5000); // 每5秒发送一次心跳包
    };

    // Event listener for receiving messages from the server
    socket.onmessage = (event) => {
        // console.log('WebSocket connection opened:', event.data);
        try {
            console.log("接收到了新数据")
            parserData(event.data as string);
        }catch(error) {
            console.error('解析数据报错:', error);
        }

    };

    // Event listener for when an error occurs
    socket.onerror = (error) => {
        console.error('WebSocket error:');
    };

    // Event listener for when the connection is closed
    socket.onclose = (event) => {
        console.log('WebSocket connection closed:', event.reason);
        wsForAmm()
        console.log('重新连接');
    };
}

export async function parserData(dataStr:string): Promise<void> {
    let data = JSON.parse(dataStr);
    let transactions = data.params?.result?.value?.block?.transactions
    if (transactions && transactions.length) {
        for (let item of transactions) {
            let err = item?.meta?.err;
            if (!err) {
                let instructions = item.transaction?.message?.instructions
                for (let instruction of instructions) {
                    if (programId == instruction?.programId) {
                        let instructionData = instruction?.data;
                        if (instructionData && (instructionData).startsWith("4Y")){
                            console.log("=====", instructionData);
                            let accounts = instruction.accounts
                            if (accounts && accounts.length >= 10) {
                                let mintA = accounts[8]
                                let mintB = accounts[9]
                                let mint = ""
                                if (mintBase != mintA) {
                                    mint= mintA
                                } else if (mintBase == mintB) {
                                    mint= mintA
                                }
                                console.log("签名是：", item.transaction?.signatures);
                                if (mint) {
                                    console.log("监听到了新币：", mint);
                                    if (curBuy < maxBuy) {
                                        console.log("执行购买逻辑");
                                        curBuy++
                                        apiSwap(mint)
                                        apiSwapSell(mint)
                                        // swap2(mint)
                                    }
                                }
                            }
                        }
                        break
                    }
                }
            }
        }
    }
}

export async function swap2(mint:string): Promise<void>  {
    console.log("开始执行swap交易...")
    await sleep(3000)
    let poolId = await getPoolId2(raydium, NATIVE_MINT.toBase58(), mint); //查询交易对
    if (!poolId) {
        console.log("未找到交易对")
        return
    }
    console.log("开始执行swap交易", amount, poolId)
    await swap(raydium, amount, poolId)
}

export const swap = async (raydium: Raydium, amountIn: number, poolId: string) => {
    let curProgramId = "";
    if (!curProgramId) {
        let data = await raydium.api.fetchPoolById({ ids: poolId });
        if (data && data.length > 0) {
            console.log("==== fetchPoolById ===", data)
            curProgramId = data[0].programId
        }
    }
    if (curProgramId) {
        if (isValidAmm(curProgramId)) {
            console.info("使用amm交易")
            ammSwap(raydium, amount, poolId)
        } else if (isValidCpmm(curProgramId)) {
            console.info("使用Cpmm交易")
            cpmmSwap(raydium, amount, poolId)
        }  else if (isValidCpmm(curProgramId)) {
            console.info("使用Clmm交易")
            clmmSwap(raydium, amount, poolId)
        } else {
            console.info("不支持的swap类型")
        }
    } else {
        console.log("未找到交易池")
    }
}



export const getPoolId2 = async (raydium:Raydium, inputMint: string, outputMint: string):Promise<string> => {
    const url = `${API_URLS.SWAP_HOST}/compute/swap-base-in?inputMint=${inputMint}&outputMint=${outputMint}&amount=${amount}&slippageBps=${slippage * 100}&txVersion=V0`
    console.info("===查询的url==", url)
    const data = await axios.get(url).then((res) => {
        console.info("===查询的pool数据是==", res.data)
        return res.data
    }).catch((error) => {
        console.log("查询报错", error)
    })
    //console.info("===查询的pool数据是1111==", data)
    if (data && data.data && data.data.routePlan && data.data.routePlan.length > 0) {
        //console.info("===查询的poolid数据是222==", data.data.routePlan[0])
        return data.data.routePlan[0].poolId
    }
    return "";
}


export const clmmSwap = async (raydium: Raydium, amountIn: number, poolId: string) => {
    let poolInfo: ApiV3PoolInfoConcentratedItem
    // RAY-USDC pool
    const inputMint = RAYMint.toBase58()
    let poolKeys: ClmmKeys | undefined
    let clmmPoolInfo: ComputeClmmPoolInfo
    let tickCache: ReturnTypeFetchMultiplePoolTickArrays

    const inputAmount = new BN(amountIn)

    if (raydium.cluster === 'mainnet') {
        // note: api doesn't support get devnet pool info, so in devnet else we go rpc method
        // if you wish to get pool info from rpc, also can modify logic to go rpc method directly
        const data = await raydium.api.fetchPoolById({ ids: poolId })
        poolInfo = data[0] as ApiV3PoolInfoConcentratedItem
        if (!isValidClmm(poolInfo.programId)) {
            console.info('target pool is not CLMM pool')
            return
        }

        clmmPoolInfo = await PoolUtils.fetchComputeClmmInfo({
            connection: raydium.connection,
            poolInfo,
        })
        tickCache = await PoolUtils.fetchMultiplePoolTickArrays({
            connection: raydium.connection,
            poolKeys: [clmmPoolInfo],
        })
    } else {
        const data = await raydium.clmm.getPoolInfoFromRpc(poolId)
        poolInfo = data.poolInfo
        poolKeys = data.poolKeys
        clmmPoolInfo = data.computePoolInfo
        tickCache = data.tickData
    }

    if (inputMint !== poolInfo.mintA.address && inputMint !== poolInfo.mintB.address) {
        console.info('input mint does not match pool')
        return
    }

    const baseIn = inputMint === poolInfo.mintA.address

    const { minAmountOut, remainingAccounts } = await PoolUtils.computeAmountOutFormat({
        poolInfo: clmmPoolInfo,
        tickArrayCache: tickCache[poolId],
        amountIn: inputAmount,
        tokenOut: poolInfo[baseIn ? 'mintB' : 'mintA'],
        slippage: slippage,
        epochInfo: await raydium.fetchEpochInfo(),
    })

    const { execute } = await raydium.clmm.swap({
        poolInfo,
        poolKeys,
        inputMint: poolInfo[baseIn ? 'mintA' : 'mintB'].address,
        amountIn: inputAmount,
        amountOutMin: minAmountOut.amount.raw,
        observationId: clmmPoolInfo.observationId,
        ownerInfo: {
            useSOLBalance: true, // if wish to use existed wsol token account, pass false
        },
        remainingAccounts,
        txVersion,

        // optional: set up priority fee here
        // computeBudgetConfig: {
        //   units: 600000,
        //   microLamports: 465915,
        // },

        // optional: add transfer sol to tip account instruction. e.g sent tip to jito
        // txTipConfig: {
        //   address: new PublicKey('96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5'),
        //   amount: new BN(10000000), // 0.01 sol
        // },
    })
    const { txId } = await execute()
    console.log('swapped in clmm pool:', { txId: `https://explorer.solana.com/tx/${txId}` })
    // process.exit() // if you don't want to end up node execution, comment this line
}

export const cpmmSwap = async (raydium: Raydium, amountIn: number, poolId: string) => {
    const inputAmount = new BN(amountIn)
    const inputMint = NATIVE_MINT.toBase58()

    let poolInfo: ApiV3PoolInfoStandardItemCpmm
    let poolKeys: CpmmKeys | undefined
    let rpcData: CpmmRpcData

    if (raydium.cluster === 'mainnet') {
        // note: api doesn't support get devnet pool info, so in devnet else we go rpc method
        // if you wish to get pool info from rpc, also can modify logic to go rpc method directly
        const data = await raydium.api.fetchPoolById({ ids: poolId })
        poolInfo = data[0] as ApiV3PoolInfoStandardItemCpmm
        if (!isValidCpmm(poolInfo.programId)) {
            console.info('target pool is not CPMM pool')
            return
        }
        rpcData = await raydium.cpmm.getRpcPoolInfo(poolInfo.id, true)
    } else {
        const data = await raydium.cpmm.getPoolInfoFromRpc(poolId)
        poolInfo = data.poolInfo
        poolKeys = data.poolKeys
        rpcData = data.rpcData
    }

    if (inputMint !== poolInfo.mintA.address && inputMint !== poolInfo.mintB.address){
        console.info('input mint does not match pool')
        return
    }

    const baseIn = inputMint === poolInfo.mintA.address

    // swap pool mintA for mintB
    const swapResult = CurveCalculator.swap(
        inputAmount,
        baseIn ? rpcData.baseReserve : rpcData.quoteReserve,
        baseIn ? rpcData.quoteReserve : rpcData.baseReserve,
        rpcData.configInfo!.tradeFeeRate
    )

    /**
     * swapResult.sourceAmountSwapped -> input amount
     * swapResult.destinationAmountSwapped -> output amount
     * swapResult.tradeFee -> this swap fee, charge input mint
     */

    const { execute } = await raydium.cpmm.swap({
        poolInfo,
        poolKeys,
        inputAmount,
        swapResult,
        slippage: slippage, // range: 1 ~ 0.0001, means 100% ~ 0.01%
        baseIn,
        // optional: set up priority fee here
        // computeBudgetConfig: {
        //   units: 600000,
        //   microLamports: 4659150,
        // },

        // optional: add transfer sol to tip account instruction. e.g sent tip to jito
        // txTipConfig: {
        //   address: new PublicKey('96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5'),
        //   amount: new BN(10000000), // 0.01 sol
        // },
    })

    printSimulateInfo()
    // don't want to wait confirm, set sendAndConfirm to false or don't pass any params to execute
    const { txId } = await execute({ sendAndConfirm: true })
    console.log(`swapped: ${poolInfo.mintA.symbol} to ${poolInfo.mintB.symbol}:`, {
        txId: `https://explorer.solana.com/tx/${txId}`,
    })

}






export const ammSwap = async (raydium: Raydium, amountIn: number, poolId: string) => {
    // const raydium = await initSdk()
    // const amountIn = 500
    const inputMint = NATIVE_MINT.toBase58()
    // const poolId = '58oQChx4yWmvKdwLLZzBi4ChoCc2fqCUWBkwMihLYQo2' // SOL-USDC pool

    let poolInfo: ApiV3PoolInfoStandardItem | undefined
    // let poolInfo: any
    let poolKeys: AmmV4Keys | undefined
    let rpcData: AmmRpcData

    if (raydium.cluster === 'mainnet') {
        // note: api doesn't support get devnet pool info, so in devnet else we go rpc method
        // if you wish to get pool info from rpc, also can modify logic to go rpc method directly
        const data = await raydium.api.fetchPoolById({ ids: poolId })
        poolInfo = data[0] as ApiV3PoolInfoStandardItem
        if (!isValidAmm(poolInfo.programId)) {
            console.info('target pool is not AMM pool')
            return
        }
        poolKeys = await raydium.liquidity.getAmmPoolKeys(poolId)
        rpcData = await raydium.liquidity.getRpcPoolInfo(poolId)
    } else {
        // note: getPoolInfoFromRpc method only return required pool data for computing not all detail pool info
        const data = await raydium.liquidity.getPoolInfoFromRpc({ poolId })
        poolInfo = data.poolInfo
        poolKeys = data.poolKeys
        rpcData = data.poolRpcData
    }
    const [baseReserve, quoteReserve, status] = [rpcData.baseReserve, rpcData.quoteReserve, rpcData.status.toNumber()]

    if (poolInfo.mintA.address !== inputMint && poolInfo.mintB.address !== inputMint) {
        console.info('input mint does not match pool')
        return
    }


    const baseIn = inputMint === poolInfo.mintA.address
    const [mintIn, mintOut] = baseIn ? [poolInfo.mintA, poolInfo.mintB] : [poolInfo.mintB, poolInfo.mintA]

    const out = raydium.liquidity.computeAmountOut({
        poolInfo: {
            ...poolInfo,
            baseReserve,
            quoteReserve,
            status,
            version: 4,
        },
        amountIn: new BN(amountIn),
        mintIn: mintIn.address,
        mintOut: mintOut.address,
        slippage: slippage, // range: 1 ~ 0.0001, means 100% ~ 0.01%
    })

    console.log(
        `computed swap ${new Decimal(amountIn)
            .div(10 ** mintIn.decimals)
            .toDecimalPlaces(mintIn.decimals)
            .toString()} ${mintIn.symbol || mintIn.address} to ${new Decimal(out.amountOut.toString())
            .div(10 ** mintOut.decimals)
            .toDecimalPlaces(mintOut.decimals)
            .toString()} ${mintOut.symbol || mintOut.address}, minimum amount out ${new Decimal(out.minAmountOut.toString())
            .div(10 ** mintOut.decimals)
            .toDecimalPlaces(mintOut.decimals)} ${mintOut.symbol || mintOut.address}`
    )

    const { execute } = await raydium.liquidity.swap({
        poolInfo,
        poolKeys,
        amountIn: new BN(amountIn),
        amountOut: out.minAmountOut, // out.amountOut means amount 'without' slippage
        fixedSide: 'in',
        inputMint: mintIn.address,
        txVersion,

        // optional: set up token account
        // config: {
        //   inputUseSolBalance: true, // default: true, if you want to use existed wsol token account to pay token in, pass false
        //   outputUseSolBalance: true, // default: true, if you want to use existed wsol token account to receive token out, pass false
        //   associatedOnly: true, // default: true, if you want to use ata only, pass true
        // },

        // optional: set up priority fee here
        // computeBudgetConfig: {
        //   units: 600000,
        //   microLamports: 46591500,
        // },

        // optional: add transfer sol to tip account instruction. e.g sent tip to jito
        // txTipConfig: {
        //   address: new PublicKey('96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5'),
        //   amount: new BN(10000000), // 0.01 sol
        // },
    })

    printSimulateInfo()
    // don't want to wait confirm, set sendAndConfirm to false or don't pass any params to execute
    const { txId } = await execute({ sendAndConfirm: true })
    console.log(`swap successfully in amm pool:`, { txId: `https://explorer.solana.com/tx/${txId}` })

    //process.exit() // if you don't want to end up node execution, comment this line
}

function sleep(ms:number) {
    return new Promise(resolve => setTimeout(resolve, ms));
}


export async function apiSwapBaseOut(mint:string) : Promise<void> {
    const inputMint = NATIVE_MINT.toBase58()
    const outputMint = mint // RAY
    const slippage = 0.3 // in percent, for this example, 0.5 means 0.5%
    const txVersion: string = 'V0' // or LEGACY
    const isV0Tx = txVersion === 'V0'

    const [isInputSol, isOutputSol] = [inputMint === NATIVE_MINT.toBase58(), outputMint === NATIVE_MINT.toBase58()]

    const { tokenAccounts } = await fetchTokenAccountData()

    const inputTokenAcc = tokenAccounts.find((a) => a.mint.toBase58() === inputMint)?.publicKey
    let outAcc = tokenAccounts.find((a) => a.mint.toBase58() === outputMint);
    const outputTokenAcc = outAcc?.publicKey

    if (!inputTokenAcc && !isInputSol) {
        console.error('do not have input token account')
        return
    }
    const amount = outAcc?.amount
    if (!amount) {
        console.error('余额小于0')
        return
    }

    // get statistical transaction fee from api
    /**
     * vh: very high
     * h: high
     * m: medium
     */
    const { data } = await axios.get<{
        id: string
        success: boolean
        data: { default: { vh: number; h: number; m: number } }
    }>(`${API_URLS.BASE_HOST}${API_URLS.PRIORITY_FEE}`)

    const { data: swapResponse } = await axios.get<SwapCompute>(
        `${
            API_URLS.SWAP_HOST
        }/compute/swap-base-out?inputMint=${inputMint}&outputMint=${outputMint}&amount=${amount}&slippageBps=${
            slippage * 100
        }&txVersion=${txVersion}`
    )
    console.log(swapResponse)
    // in swapBaseOut, swapResponse.inputAmount means how much expected to cost and swapResponse.otherAmountThreshold means 'maximum' will cost in this swap

    const { data: swapTransactions } = await axios.post<{
        id: string
        version: string
        success: boolean
        data: { transaction: string }[]
    }>(`${API_URLS.SWAP_HOST}/transaction/swap-base-out`, {
        computeUnitPriceMicroLamports: String(data.data.default.h),
        swapResponse,
        txVersion,
        wallet: owner.publicKey.toBase58(),
        wrapSol: isInputSol,
        unwrapSol: isOutputSol, // true means output mint receive sol, false means output mint received wsol
        inputAccount: isInputSol ? undefined : inputTokenAcc?.toBase58(),
        outputAccount: isOutputSol ? undefined : outputTokenAcc?.toBase58(),
    })

    const allTxBuf = swapTransactions.data.map((tx) => Buffer.from(tx.transaction, 'base64'))
    const allTransactions = allTxBuf.map((txBuf) =>
        isV0Tx ? VersionedTransaction.deserialize(txBuf) : Transaction.from(txBuf)
    )

    console.log(`total ${allTransactions.length} transactions`, swapTransactions)

    let idx = 0
    if (!isV0Tx) {
        for (const tx of allTransactions) {
            console.log(`${++idx} transaction sending...`)
            const transaction = tx as Transaction
            transaction.sign(owner)
            const txId = await sendAndConfirmTransaction(connection, transaction, [owner], { skipPreflight: true })
            console.log(`${++idx} transaction confirmed, txId: ${txId}`)
        }
    } else {
        for (const tx of allTransactions) {
            idx++
            const transaction = tx as VersionedTransaction
            transaction.sign([owner])
            const txId = await connection.sendTransaction(tx as VersionedTransaction, { skipPreflight: true })
            const { lastValidBlockHeight, blockhash } = await connection.getLatestBlockhash({
                commitment: 'finalized',
            })
            console.log(`${idx} transaction sending..., txId: ${txId}`)
            await connection.confirmTransaction(
                {
                    blockhash,
                    lastValidBlockHeight,
                    signature: txId,
                },
                'confirmed'
            )
            console.log(`${idx} transaction confirmed`)
        }
    }
}




main()
// wsForAmm()

// wsForCpmm()

// getTokenAccountsByMint();
